/*-------------------------------------------------------------------------
 - Copyright (c) 2024-2025 [XD-AMCC TEAM]
 - [XD-AMCC] is licensed under Mulan PSL v2.
 - You can use this software according to the terms and conditions of the Mulan PSL v2.
 - You may obtain a copy of Mulan PSL v2 at:
 -          http://license.coscl.org.cn/MulanPSL2
 - THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES
 - OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
 - TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 - See the Mulan PSL v2 for more details.
 ------------------------------------------------------------------------*/

/**
 * @brief Implementation of the code-manager component.
 *
 * There are 2 parts in this file:
 * 1. module level:  management the address objects pool,
 *    All address object in three-address-codes are stored in this pool.
 *    Introducing this pool makes it easy to destroy all address objects,
 *    and also reduces memory fragmentation.
 * 2. class ircodemgr: emit all IR-code.
 *
 * @author  WXQ#XDU
 * @date    2025.01
 */

#include <stdlib.h>

#include "_emit_inner.h"


// -------------------------------------------------------------------------
//  Part 1   Module code of code-manager
//

#define  SZ_ADDR_TRUCK   500
typedef struct {
    size_t        n_used;
    t_ir_address  slot[SZ_ADDR_TRUCK];
} t_addr_truck;

// address pool, like a train with many trucks
typedef struct {
    t_list_ptr     addressTruckList;  // list<t_addr_truck*>
    t_addr_truck * currentTruck;     // current used
} t_addr_pool;


static t_addr_pool  gAddrPool;

// Initialization module level data
void  codemgr_module_init() {
    gAddrPool.addressTruckList  = list_new(free );
    gAddrPool.currentTruck      = NULL;
    log_trace("  Address object pool has initialized");
}


// Destroy module level data
void codemgr_module_clean(){
    list_destroy( gAddrPool.addressTruckList );
    gAddrPool.addressTruckList  = NULL;
    gAddrPool.currentTruck      = NULL;
    log_trace("  Address object pool has destroyed");
}

// Return an empty slot for one new address object
static  t_ir_address_ptr  addrPool_get_slot() {
    if (  NULL == gAddrPool.currentTruck
       || gAddrPool.currentTruck->n_used == SZ_ADDR_TRUCK )
    {
        t_addr_truck * truck = (t_addr_truck*)calloc(1, sizeof(t_addr_truck));
        list_append(gAddrPool.addressTruckList, truck);
        gAddrPool.currentTruck = truck;
    }

    size_t  idx = (gAddrPool.currentTruck->n_used)++;
    t_ir_address_ptr slot = &(gAddrPool.currentTruck->slot[idx]);
    return slot;
}

// -------------------------------------------------------------------------
//  Part 2   Implementation of class code-manager
//


// the app-value is IR-codes of a function,
// it has been recorded in codemgr_exit_func()
static void destroy_func_codes(t_list_app_value appValue) {
    t_func_code_pair * funcCodePair = (t_func_code_pair *)appValue;
    if (NULL != funcCodePair) {
        const char * unitName;
        const char * funcName;
        if ( tree_is_root(funcCodePair->declNode) ) { // translation unit
            funcName = "";
            unitName = CODEDESC_GLOBSCOPE;
        } else {
            t_tree_ptr  funcNameNode = tree_get_child(funcCodePair->declNode, 1);
            funcName = tree_get_markText(funcNameNode);
            unitName = "function";
        }

        list_destroy(funcCodePair->codes);
        log_trace("  destroyed  codes for %s %s", unitName, funcName);
    }
}


t_codemgr_ptr  codemgr_new(t_emitter_ptr emitterPtr) {
    t_codemgr_ptr  pThis = (t_codemgr_ptr)calloc(1, sizeof(*pThis));
    if (NULL == pThis)
        return NULL;

    pThis->emitterPtr   = emitterPtr;
    pThis->programCodes = list_new( destroy_func_codes );

    pThis->trueAddress  = codemgr_newNumber(pThis, 1);
    pThis->falseAddress = codemgr_newNumber(pThis, 0);

    return pThis;
}


void codemgr_destroy(t_codemgr_ptr pThis) {
    codemgr_destroy_codes( pThis->programCodes );
    free(pThis);
}


// destroy a set of IR-codes
void codemgr_destroy_codes(t_program_codes programCodes) {
    if (NULL == programCodes) return;

    log_trace("DESTROY-ing IR-codes of source program ...");
    list_destroy(programCodes); // will call back destroy_func_codes()
    log_debug("DESTROY-ed  IR-codes of source program");
}


t_ircode_id  codemgr_nextstat(t_codemgr_ptr pThis) {
    return pThis->emitterPtr->currFuncInfo->nextCodeId;
}



t_ir_address_ptr codemgr_newTempVar(t_codemgr_ptr pThis) {
    t_tempvar_id    tempvar = tempmgr_newTemp(pThis->emitterPtr->tempmgrPtr);
    t_ir_address_ptr   addr = addrPool_get_slot();
    addr->addrKind          = AK_TEMPVAR;
    addr->value.tempvarId   = tempvar;
    return addr;
}

t_ir_address_ptr codemgr_newNumber(t_codemgr_ptr pThis, t_ircode_id  num) {
    t_ir_address_ptr   addr  = addrPool_get_slot();
    addr->addrKind           = AK_IRCODE;
    addr->value.targetCodeId = num;
    return addr;
}

t_ir_address_ptr codemgr_newToken(t_codemgr_ptr pThis, t_tree_ptr tokenNode) {
    t_ir_address_ptr   addr = addrPool_get_slot();
    addr->addrKind          = AK_TOKEN;
    addr->value.treeNode    = tokenNode;
    return addr;
}


t_ir_address_ptr codemgr_unknownJumpAddress(t_codemgr_ptr pThis) {
    t_ir_address_ptr   addr  = addrPool_get_slot();
    addr->addrKind           = AK_IRCODE;
    addr->value.targetCodeId = 0;
    return addr;
}

const char * dumpIRcode_codeName(EnumOpCode opcode); // in code_dump.c

static char * quad2String(t_ircode_ptr codePtr) {
    static char buffer[200] = {0}; // big enough ?
    char * dest = buffer;
    const char * codeName = dumpIRcode_codeName(codePtr->opcode);
    if (NULL != codeName)
        sprintf(dest, "(%u) ( %s", codePtr->id, codeName );
    else
        sprintf(dest, "(%u) ( %d", codePtr->id, codePtr->opcode );
    dest += strlen(dest);

    t_token_ptr tokenPtr;
    for (int idxAddr = 0; idxAddr < 3; ++idxAddr) {
        t_ir_address * address = (codePtr->address[idxAddr]);
        if ( NULL == address ) {
            sprintf(dest, ",   ");
        }
        else switch(address->addrKind) {
            case AK_IRCODE:   // IR-code's id
                sprintf(dest, ", %u", address->value.targetCodeId);
                break;
            case AK_TEMPVAR:  // compiler-generated temporary variable
                sprintf(dest, ", %s%u",
                            amcc_get_tempvar_prefix(),
                            address->value.tempvarId);
                break;
            case AK_TOKEN:  // a variable or literal in source-program
                tokenPtr = tree_get_token(address->value.treeNode);
                sprintf(dest, ", %s", token_get_text(tokenPtr));
                break;
            default:
                sprintf(dest, ", badAddrKind %d",  address->addrKind);
                break;
        }
        dest += strlen(dest);
    }
    strcat(dest, " )");

    return buffer;
}

t_ircode_ptr codemgr_emit(t_codemgr_ptr pThis, EnumOpCode op,
                          t_ir_address_ptr arg1,
                          t_ir_address_ptr arg2,
                          t_ir_address_ptr result)
{
    t_ircode_ptr  code = (t_ircode_ptr)calloc(1, sizeof(*code));
    if (NULL == code)  return NULL;

    t_emitter_funcinfo * funcinfo = pThis->emitterPtr->currFuncInfo;
    code->opcode = op;
    code->id     = (funcinfo->nextCodeId)++ ;
    code->address[AI_ARG1]   = arg1;
    code->address[AI_ARG2]   = arg2;
    code->address[AI_RES]    = result;
    code->comment.isConstant = 0;
    code->comment.text       = NULL;

    list_append(funcinfo->ircodes, code);
    log_trace("    emit: %s", quad2String(code) );
    return code;
}


// For an ID(name) node, query type-info from the symbol table entry,
// For an expr node, query type-info from the type-pool.
//
t_type_ptr codemgr_getDataType(t_tree_ptr node) {
    t_symentry_ptr symentryPtr = tree_get_symbolEntry(node);
    if (NULL != symentryPtr) {
        return symentry_type(symentryPtr);
    }

    return EXPRTYPE_GET(node);
}



static void add_last_return_(t_codemgr_ptr pThis, t_emitter_funcinfo * funcinfo);


void codemgr_entry_func(t_codemgr_ptr pThis, t_tree_ptr funcDeclNode) {
    // do nothing
    // Just declaring a symmetrical logical behavior
}

void codemgr_exit_func(t_codemgr_ptr pThis) {
    t_emitter_funcinfo * funcinfo = pThis->emitterPtr->currFuncInfo;

    if ( tree_is_markedByNT(funcinfo->funcDeclNode, NT_funcDeclaration) ) {
        add_last_return_(pThis, funcinfo); // append return instruction on demand

        // Backpatch .nc of function-body, a compoundStatement.
        // Why?
        // If the last statement of function-body is IF, IF_ELSE, or WHILE,
        // a few incomplete goto instructions exists in compoundStatement.nc.
        //
        t_tree_ptr   compoundStmt = tree_get_child(funcinfo->funcDeclNode, 5);
        t_codeattr_ptr   stmtAttr = (t_codeattr_ptr) tree_get_annotation(compoundStmt);
        emitter_backpatch(stmtAttr->nextChain,
                codemgr_nextstat(pThis) - 1, // the last return instruction
                "func-body" CHAIN_NC );
    }

    t_func_code_pair *
    funcCodePair = (t_func_code_pair *)calloc(1, sizeof(*funcCodePair));
    funcCodePair->declNode = funcinfo->funcDeclNode;
    funcCodePair->codes    = funcinfo->ircodes; // move codes to code-manager
    list_append (pThis->programCodes, funcCodePair);
    // ^^^ the pair will be destroyed by destroy_func_codes()

    // Broadcast: Don't destroy code list through this field
    funcinfo->ircodes = NULL;
}


// Append an extra return-instruction for a function
// IFF the last ir-code of the function is not a "return".
//
static void add_last_return_(t_codemgr_ptr pThis, t_emitter_funcinfo * funcinfo)
{
    t_ircode_ptr codePtr = list_get_last_value(funcinfo->ircodes);
    if ( NULL != codePtr ) {
        if (    OP_RETURN     == codePtr->opcode
            ||  OP_RETURN_VAL == codePtr->opcode )
        {
            return;  // last instruction is return
        }
    }

    // emit a return instruction
    t_tree_ptr funcNameNode   = tree_get_child(funcinfo->funcDeclNode, 1);
    t_type_ptr funcType       = codemgr_getDataType(funcNameNode);
    t_type_ptr funcReturnType = type_func_return(funcType);
    if ( TK_TYPE_VOID == type_var_basekind(funcReturnType) ) {
        // If a function returns "void", emit "return"
        codePtr = codemgr_emit ( pThis, OP_RETURN, NULL, NULL, NULL);
    } else {
        // If a function should return a value, emit "return flag_value"
        codePtr = codemgr_emit ( pThis, OP_RETURN_VAL , NULL, NULL,
                            codemgr_newNumber(pThis, RETCODE_MISS_RETURN) );

        // fire a warning report, but AMCC ignores this error
        t_position pos = tree_get_stopPosition(funcinfo->funcDeclNode);
        ERROR_REPORT_INPUT(pos.filePath, pos.line, pos.column, EC_MISSRET_STMT,
                           tree_get_markText(funcNameNode));
    }
    codePtr->comment.text = CODEDESC_ADDRET;

}
